{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# -*- coding: utf-8 -*-\n",
    "#############from  MLia chap11 Apriori\n",
    "\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "from numpy import * \n",
    "#import apriori\n",
    "\n",
    "import os \n",
    "os.chdir(\"/home/lab466/pythons/pyMLIA35/Ch11Apriori\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def loadDataSet():\n",
    "    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# C1 是大小为1的所有候选项集的集合\n",
    "def createC1(dataSet):\n",
    "    C1 = []\n",
    "    for transaction in dataSet:\n",
    "        for item in transaction:\n",
    "            if not [item] in C1:\n",
    "                C1.append([item]) #store all the item unrepeatly\n",
    "\n",
    "    C1.sort()\n",
    "    #return map(frozenset, C1)#frozen set, user can't change it.\n",
    "    return list(map(frozenset, C1))\n",
    "\n",
    "def scanD(D,Ck,minSupport):\n",
    "    ssCnt={}\n",
    "    for tid in D:\n",
    "        for can in Ck:\n",
    "            if can.issubset(tid):\n",
    "                #if not ssCnt.has_key(can):\n",
    "                if not can in ssCnt:\n",
    "                    ssCnt[can]=1\n",
    "                else: ssCnt[can]+=1\n",
    "    numItems=float(len(D))\n",
    "    retList = []\n",
    "    supportData = {}\n",
    "    for key in ssCnt:\n",
    "        support = ssCnt[key]/numItems #compute support\n",
    "        if support >= minSupport:\n",
    "            retList.insert(0,key)\n",
    "        supportData[key] = support\n",
    "    return retList, supportData"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#total apriori\n",
    "def aprioriGen(Lk, k): #组合，向上合并\n",
    "    #creates Ck 参数：频繁项集列表 Lk 与项集元素个数 k\n",
    "    retList = []\n",
    "    lenLk = len(Lk)\n",
    "    for i in range(lenLk):\n",
    "        for j in range(i+1, lenLk): #两两组合遍历\n",
    "            L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]\n",
    "            L1.sort(); L2.sort()\n",
    "            if L1==L2: #若两个集合的前k-2个项相同时,则将两个集合合并\n",
    "                retList.append(Lk[i] | Lk[j]) #set union\n",
    "    return retList"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#apriori\n",
    "def apriori(dataSet, minSupport = 0.5):\n",
    "    C1 = createC1(dataSet)\n",
    "    D = list(map(set, dataSet)) #python3\n",
    "    L1, supportData = scanD(D, C1, minSupport)#单项最小支持度判断 0.5，生成L1\n",
    "    L = [L1]\n",
    "    k = 2\n",
    "    while (len(L[k-2]) > 0):#创建包含更大项集的更大列表,直到下一个大的项集为空\n",
    "        Ck = aprioriGen(L[k-2], k)#Ck\n",
    "        Lk, supK = scanD(D, Ck, minSupport)#get Lk\n",
    "        supportData.update(supK)\n",
    "        L.append(Lk)\n",
    "        k += 1 #继续向上合并 生成项集个数更多的\n",
    "    return L, supportData"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#生成关联规则\n",
    "def generateRules(L, supportData, minConf=0.7):\n",
    "    #频繁项集列表、包含那些频繁项集支持数据的字典、最小可信度阈值\n",
    "    bigRuleList = [] #存储所有的关联规则\n",
    "    for i in range(1, len(L)):  #只获取有两个或者更多集合的项目，从1,即第二个元素开始，L[0]是单个元素的\n",
    "        # 两个及以上的才可能有关联一说，单个元素的项集不存在关联问题\n",
    "        for freqSet in L[i]:\n",
    "            H1 = [frozenset([item]) for item in freqSet]\n",
    "            #该函数遍历L中的每一个频繁项集并对每个频繁项集创建只包含单个元素集合的列表H1\n",
    "            if (i > 1):\n",
    "            #如果频繁项集元素数目超过2,那么会考虑对它做进一步的合并\n",
    "                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)\n",
    "            else:#第一层时，后件数为1\n",
    "                calcConf(freqSet, H1, supportData, bigRuleList, minConf)# 调用函数2\n",
    "    return bigRuleList"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#生成候选规则集合：计算规则的可信度以及找到满足最小可信度要求的规则\n",
    "def calcConf(freqSet, H, supportData, brl, minConf=0.7):\n",
    "    #针对项集中只有两个元素时，计算可信度\n",
    "    prunedH = []#返回一个满足最小可信度要求的规则列表\n",
    "    for conseq in H:#后件，遍历 H中的所有项集并计算它们的可信度值\n",
    "        conf = supportData[freqSet]/supportData[freqSet-conseq] #可信度计算，结合支持度数据\n",
    "        if conf >= minConf:\n",
    "            print (freqSet-conseq,'-->',conseq,'conf:',conf)\n",
    "            #如果某条规则满足最小可信度值,那么将这些规则输出到屏幕显示\n",
    "            brl.append((freqSet-conseq, conseq, conf))#添加到规则里，brl 是前面通过检查的 bigRuleList\n",
    "            prunedH.append(conseq)#同样需要放入列表到后面检查\n",
    "    return prunedH"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#合并\n",
    "def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):\n",
    "    #参数:一个是频繁项集,另一个是可以出现在规则右部的元素列表 H\n",
    "    m = len(H[0])\n",
    "    if (len(freqSet) > (m + 1)): #频繁项集元素数目大于单个集合的元素数\n",
    "        Hmp1 = aprioriGen(H, m+1)#存在不同顺序、元素相同的集合，合并具有相同部分的集合\n",
    "        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)#计算可信度\n",
    "        if (len(Hmp1) > 1):    #满足最小可信度要求的规则列表多于1,则递归\n",
    "            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "##P205  发现频繁集\n",
    "\n",
    "## 导入数据集\n",
    "dataSet=loadDataSet()\n",
    "dataSet\n",
    "## 构建第一个候选项集合\n",
    "C1=createC1(dataSet)\n",
    "C1\n",
    "## 构建集合表示的数据集\n",
    "D=list(map(set,dataSet))\n",
    "D\n",
    "## 设置支持度0.5\n",
    "L1,suppData0=scanD(D, C1, 0.5)\n",
    "L1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## P207组织完整的Apriori算法\n",
    "##reload(apriori)\n",
    "## 测试Aprior算法\n",
    "L,suppData=apriori(dataSet)\n",
    " \n",
    "L\n",
    "## 查看频繁项集列表\n",
    "L[0]\n",
    "L[1]\n",
    "L[2]\n",
    "L[3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[frozenset({5}), frozenset({2}), frozenset({3})], [frozenset({2, 5})], []]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## 查看aprioriGen函数工作流程\n",
    "aprioriGen(L[0], 2)\n",
    "\n",
    "## 修改支持度0.7\n",
    "L,suppData=apriori(dataSet,minSupport=0.7)\n",
    "L"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({5}) --> frozenset({2}) conf: 1.0\n",
      "frozenset({2}) --> frozenset({5}) conf: 1.0\n",
      "frozenset({1}) --> frozenset({3}) conf: 1.0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[(frozenset({5}), frozenset({2}), 1.0),\n",
       " (frozenset({2}), frozenset({5}), 1.0),\n",
       " (frozenset({1}), frozenset({3}), 1.0)]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## P209 从频繁项集挖掘关联规则\n",
    "##reload(apriori)\n",
    "##生成一个支持度0.5的集合 \n",
    "L,suppData=apriori(dataSet,minSupport=0.5)\n",
    "rules=generateRules(L,suppData, minConf=0.7)\n",
    "rules"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({3}) --> frozenset({2}) conf: 0.6666666666666666\n",
      "frozenset({2}) --> frozenset({3}) conf: 0.6666666666666666\n",
      "frozenset({5}) --> frozenset({3}) conf: 0.6666666666666666\n",
      "frozenset({3}) --> frozenset({5}) conf: 0.6666666666666666\n",
      "frozenset({5}) --> frozenset({2}) conf: 1.0\n",
      "frozenset({2}) --> frozenset({5}) conf: 1.0\n",
      "frozenset({3}) --> frozenset({1}) conf: 0.6666666666666666\n",
      "frozenset({1}) --> frozenset({3}) conf: 1.0\n",
      "frozenset({5}) --> frozenset({2, 3}) conf: 0.6666666666666666\n",
      "frozenset({3}) --> frozenset({2, 5}) conf: 0.6666666666666666\n",
      "frozenset({2}) --> frozenset({3, 5}) conf: 0.6666666666666666\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[(frozenset({3}), frozenset({2}), 0.6666666666666666),\n",
       " (frozenset({2}), frozenset({3}), 0.6666666666666666),\n",
       " (frozenset({5}), frozenset({3}), 0.6666666666666666),\n",
       " (frozenset({3}), frozenset({5}), 0.6666666666666666),\n",
       " (frozenset({5}), frozenset({2}), 1.0),\n",
       " (frozenset({2}), frozenset({5}), 1.0),\n",
       " (frozenset({3}), frozenset({1}), 0.6666666666666666),\n",
       " (frozenset({1}), frozenset({3}), 1.0),\n",
       " (frozenset({5}), frozenset({2, 3}), 0.6666666666666666),\n",
       " (frozenset({3}), frozenset({2, 5}), 0.6666666666666666),\n",
       " (frozenset({2}), frozenset({3, 5}), 0.6666666666666666)]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## 降低可信度阈值，然后看结果\n",
    "rules=generateRules(L,suppData, minConf=0.5)\n",
    "rules"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "## P220 有毒蘑菇相似性发现\n",
    "mushDatSet = [line.split() for line in open('mushroom.dat').readlines()]\n",
    "# \n",
    "L,suppData=apriori(mushDatSet, minSupport=0.3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({'2', '28'})\n",
      "frozenset({'2', '53'})\n",
      "frozenset({'2', '23'})\n",
      "frozenset({'2', '34'})\n",
      "frozenset({'2', '36'})\n",
      "frozenset({'2', '59'})\n",
      "frozenset({'2', '63'})\n",
      "frozenset({'2', '67'})\n",
      "frozenset({'2', '76'})\n",
      "frozenset({'2', '85'})\n",
      "frozenset({'2', '86'})\n",
      "frozenset({'2', '90'})\n",
      "frozenset({'2', '93'})\n",
      "frozenset({'2', '39'})\n"
     ]
    }
   ],
   "source": [
    "# \n",
    "for item in L[1]:\n",
    "    if item.intersection('2'): print (item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({'2', '34', '28', '59'})\n",
      "frozenset({'2', '34', '28', '63'})\n",
      "frozenset({'2', '34', '85', '28'})\n",
      "frozenset({'2', '34', '28', '86'})\n",
      "frozenset({'2', '34', '28', '90'})\n",
      "frozenset({'2', '34', '28', '39'})\n",
      "frozenset({'2', '28', '63', '59'})\n",
      "frozenset({'2', '85', '28', '53'})\n",
      "frozenset({'2', '85', '28', '59'})\n",
      "frozenset({'2', '85', '28', '63'})\n",
      "frozenset({'2', '28', '86', '53'})\n",
      "frozenset({'2', '28', '86', '59'})\n",
      "frozenset({'2', '28', '86', '63'})\n",
      "frozenset({'2', '85', '28', '86'})\n",
      "frozenset({'2', '28', '90', '53'})\n",
      "frozenset({'2', '28', '90', '59'})\n",
      "frozenset({'2', '85', '28', '90'})\n",
      "frozenset({'2', '28', '90', '86'})\n",
      "frozenset({'2', '28', '39', '53'})\n",
      "frozenset({'2', '28', '39', '59'})\n",
      "frozenset({'2', '28', '39', '63'})\n",
      "frozenset({'2', '85', '28', '39'})\n",
      "frozenset({'2', '28', '86', '39'})\n",
      "frozenset({'2', '28', '90', '39'})\n",
      "frozenset({'2', '34', '85', '53'})\n",
      "frozenset({'2', '34', '86', '53'})\n",
      "frozenset({'2', '34', '90', '53'})\n",
      "frozenset({'2', '34', '39', '53'})\n",
      "frozenset({'2', '34', '28', '53'})\n",
      "frozenset({'2', '85', '86', '53'})\n",
      "frozenset({'2', '85', '90', '53'})\n",
      "frozenset({'2', '90', '86', '53'})\n",
      "frozenset({'2', '90', '39', '53'})\n",
      "frozenset({'2', '85', '39', '53'})\n",
      "frozenset({'2', '86', '39', '53'})\n",
      "frozenset({'2', '23', '63', '36'})\n",
      "frozenset({'2', '85', '23', '36'})\n",
      "frozenset({'2', '85', '23', '63'})\n",
      "frozenset({'2', '23', '86', '36'})\n",
      "frozenset({'2', '23', '86', '63'})\n",
      "frozenset({'2', '85', '23', '86'})\n",
      "frozenset({'2', '23', '36', '93'})\n",
      "frozenset({'2', '85', '23', '93'})\n",
      "frozenset({'2', '23', '86', '93'})\n",
      "frozenset({'2', '23', '39', '36'})\n",
      "frozenset({'2', '23', '39', '63'})\n",
      "frozenset({'2', '85', '23', '39'})\n",
      "frozenset({'2', '23', '86', '39'})\n",
      "frozenset({'2', '23', '39', '93'})\n",
      "frozenset({'2', '34', '23', '36'})\n",
      "frozenset({'2', '34', '23', '59'})\n",
      "frozenset({'2', '34', '36', '59'})\n",
      "frozenset({'2', '34', '23', '63'})\n",
      "frozenset({'2', '34', '63', '36'})\n",
      "frozenset({'2', '34', '63', '59'})\n",
      "frozenset({'2', '34', '85', '23'})\n",
      "frozenset({'2', '34', '85', '36'})\n",
      "frozenset({'2', '34', '85', '59'})\n",
      "frozenset({'2', '34', '85', '63'})\n",
      "frozenset({'2', '34', '85', '67'})\n",
      "frozenset({'2', '34', '85', '76'})\n",
      "frozenset({'2', '34', '23', '86'})\n",
      "frozenset({'2', '34', '86', '36'})\n",
      "frozenset({'2', '34', '86', '59'})\n",
      "frozenset({'2', '34', '86', '63'})\n",
      "frozenset({'2', '34', '86', '67'})\n",
      "frozenset({'2', '34', '76', '86'})\n",
      "frozenset({'2', '34', '85', '86'})\n",
      "frozenset({'2', '34', '23', '90'})\n",
      "frozenset({'2', '34', '90', '36'})\n",
      "frozenset({'2', '34', '90', '59'})\n",
      "frozenset({'2', '34', '90', '63'})\n",
      "frozenset({'2', '34', '85', '90'})\n",
      "frozenset({'2', '34', '90', '86'})\n",
      "frozenset({'2', '34', '23', '93'})\n",
      "frozenset({'2', '34', '36', '93'})\n",
      "frozenset({'2', '34', '59', '93'})\n",
      "frozenset({'2', '34', '63', '93'})\n",
      "frozenset({'2', '34', '85', '93'})\n",
      "frozenset({'2', '34', '86', '93'})\n",
      "frozenset({'2', '34', '90', '93'})\n",
      "frozenset({'2', '34', '23', '39'})\n",
      "frozenset({'2', '34', '39', '36'})\n",
      "frozenset({'2', '34', '39', '59'})\n",
      "frozenset({'2', '34', '39', '63'})\n",
      "frozenset({'2', '34', '67', '39'})\n",
      "frozenset({'2', '34', '76', '39'})\n",
      "frozenset({'2', '34', '85', '39'})\n",
      "frozenset({'2', '34', '86', '39'})\n",
      "frozenset({'2', '34', '90', '39'})\n",
      "frozenset({'2', '34', '39', '93'})\n",
      "frozenset({'2', '23', '36', '59'})\n",
      "frozenset({'2', '39', '36', '93'})\n",
      "frozenset({'2', '23', '63', '59'})\n",
      "frozenset({'2', '63', '36', '59'})\n",
      "frozenset({'2', '85', '23', '59'})\n",
      "frozenset({'2', '85', '36', '59'})\n",
      "frozenset({'2', '85', '63', '59'})\n",
      "frozenset({'2', '23', '86', '59'})\n",
      "frozenset({'2', '86', '36', '59'})\n",
      "frozenset({'2', '86', '63', '59'})\n",
      "frozenset({'2', '85', '86', '59'})\n",
      "frozenset({'2', '23', '90', '59'})\n",
      "frozenset({'2', '90', '36', '59'})\n",
      "frozenset({'2', '23', '59', '93'})\n",
      "frozenset({'2', '36', '59', '93'})\n",
      "frozenset({'2', '63', '59', '93'})\n",
      "frozenset({'2', '85', '59', '93'})\n",
      "frozenset({'2', '86', '59', '93'})\n",
      "frozenset({'2', '23', '39', '59'})\n",
      "frozenset({'2', '39', '36', '59'})\n",
      "frozenset({'2', '39', '63', '59'})\n",
      "frozenset({'2', '85', '39', '59'})\n",
      "frozenset({'2', '86', '39', '59'})\n",
      "frozenset({'2', '39', '59', '93'})\n",
      "frozenset({'2', '85', '63', '36'})\n",
      "frozenset({'2', '86', '63', '36'})\n",
      "frozenset({'2', '85', '86', '63'})\n",
      "frozenset({'2', '90', '63', '36'})\n",
      "frozenset({'2', '90', '63', '59'})\n",
      "frozenset({'2', '63', '36', '93'})\n",
      "frozenset({'2', '85', '63', '93'})\n",
      "frozenset({'2', '86', '63', '93'})\n",
      "frozenset({'2', '39', '63', '36'})\n",
      "frozenset({'2', '85', '39', '63'})\n",
      "frozenset({'2', '86', '39', '63'})\n",
      "frozenset({'2', '39', '63', '93'})\n",
      "frozenset({'2', '85', '67', '86'})\n",
      "frozenset({'2', '85', '67', '39'})\n",
      "frozenset({'2', '67', '86', '39'})\n",
      "frozenset({'2', '85', '76', '86'})\n",
      "frozenset({'2', '85', '76', '39'})\n",
      "frozenset({'2', '76', '86', '39'})\n",
      "frozenset({'2', '85', '86', '36'})\n",
      "frozenset({'2', '85', '23', '90'})\n",
      "frozenset({'2', '85', '90', '36'})\n",
      "frozenset({'2', '85', '90', '59'})\n",
      "frozenset({'2', '85', '90', '63'})\n",
      "frozenset({'2', '85', '36', '93'})\n",
      "frozenset({'2', '85', '39', '36'})\n",
      "frozenset({'2', '85', '39', '93'})\n",
      "frozenset({'2', '23', '90', '86'})\n",
      "frozenset({'2', '90', '86', '36'})\n",
      "frozenset({'2', '90', '86', '59'})\n",
      "frozenset({'2', '90', '86', '63'})\n",
      "frozenset({'2', '85', '90', '86'})\n",
      "frozenset({'2', '86', '36', '93'})\n",
      "frozenset({'2', '85', '86', '93'})\n",
      "frozenset({'2', '86', '39', '36'})\n",
      "frozenset({'2', '85', '86', '39'})\n",
      "frozenset({'2', '86', '39', '93'})\n",
      "frozenset({'2', '23', '90', '93'})\n",
      "frozenset({'2', '90', '36', '93'})\n",
      "frozenset({'2', '90', '59', '93'})\n",
      "frozenset({'2', '90', '63', '93'})\n",
      "frozenset({'2', '85', '90', '93'})\n",
      "frozenset({'2', '90', '86', '93'})\n",
      "frozenset({'2', '90', '39', '36'})\n",
      "frozenset({'2', '90', '39', '59'})\n",
      "frozenset({'2', '90', '39', '63'})\n",
      "frozenset({'2', '85', '90', '39'})\n",
      "frozenset({'2', '90', '86', '39'})\n",
      "frozenset({'2', '90', '39', '93'})\n"
     ]
    }
   ],
   "source": [
    "# \n",
    "for item in L[3]:\n",
    "    if item.intersection('2'): print (item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
